/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.sll.engagemangsindex.ws;

import java.util.ArrayList;
import se.sll.engagemangsindex.exceptions.DuplicateUpdateException;
import se.sll.engagemangsindex.exceptions.InvalidRoutingException;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jws.WebService;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.cxf.annotations.SchemaValidation;
import riv.itintegration.engagementindex._1.EngagementTransactionType;
import riv.itintegration.engagementindex._1.ResultCodeEnum;
import riv.itintegration.engagementindex.processnotificationresponder._1.ProcessNotificationType;
import riv.itintegration.engagementindex.update._1.rivtabp21.UpdateResponderInterface;
import riv.itintegration.engagementindex.updateresponder._1.UpdateResponseType;
import riv.itintegration.engagementindex.updateresponder._1.UpdateType;
import se.sll.engagemangsindex.dto.EngagementTransactionTypeImpl;
import se.sll.engagemangsindex.dto.EngagementTypeImpl;
import se.sll.engagemangsindex.ejb.ConfigurationHolder;
import se.sll.engagemangsindex.ejb.LogicalAddressCache;

/**
 *
 * @author khaleddaham
 */
@WebService(endpointInterface = "riv.itintegration.engagementindex.update._1.rivtabp21.UpdateResponderInterface",
        serviceName = "Update", wsdlLocation = "META-INF/UpdateInteraction_1.0_RIVTABP21.wsdl")
// @TODO run numbers on how much this really impacts performance
@SchemaValidation
@Slf4j
public class UpdateWSImpl extends BaseUpdateImpl implements UpdateResponderInterface {

    @Inject
    ConfigurationHolder configuration;
    @Inject
    LogicalAddressCache logicalAddressCache;
    @Resource(mappedName = "java:/JmsXA")
    private ConnectionFactory cf;
    @Resource(mappedName = "java:/JmsXA")
    private ConnectionFactory camelJMSConnectionFactory;
    @Resource(mappedName = "java:/queue/ei")
    private Queue eiQueue;
    private Connection connection = null;
    private Session session = null;
    private MessageProducer publisher = null;
    private CamelContext camelContext = null;

    /**
     *
     */
    public UpdateWSImpl() throws Exception {
        super();
    }

    /**
     *
     * @param logicalAddress
     * @param parameters
     * @return
     */
    @Override
    public UpdateResponseType update(String logicalAddress, UpdateType parameters) {
        UpdateResponseType response = new UpdateResponseType();
        response.setComment(null);
        response.setResultCode(ResultCodeEnum.OK);

        if (parameters.getEngagementTransaction().size() == 0) {
            return response;
        }
        List<EngagementTransactionTypeImpl> engagementTransactionList = this.copy(parameters.getEngagementTransaction());

        if (camelContext == null && configuration.hasFeatureProcessNotification()) {
            try {
                this.buildCamelRoute();
            } catch (Exception ex) {
                log.error("Unable to start camel route, processnotification will not work!");
            }
        }

        try {
            String incomingOwner = engagementTransactionList.get(0).getEngagement().getOwner();
            this.updateAllEngagements(logicalAddress, engagementTransactionList);
            if (configuration.hasFeatureProcessNotification()) {
                this.sendMessage(incomingOwner, parameters);
            }
        } catch (InvalidRoutingException ex) {
            log.info(ex.getMessage(), ex);
            response.setComment(ex.getMessage());
            response.setResultCode(ResultCodeEnum.ERROR);
        } catch (DuplicateUpdateException ex) {
            log.info(ex.getMessage(), ex);
            response.setComment(ex.getMessage());
            response.setResultCode(ResultCodeEnum.ERROR);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            this.throwSoapFaultException(ex.getMessage());
        }
        return response;
    }

    /**
     *
     * @param logicalAddress
     * @param parameters
     */
    protected void updateAllEngagements(String logicalAddress, List<EngagementTransactionTypeImpl> engagementTransactionList) throws InvalidRoutingException, DuplicateUpdateException {
        this.checkForMatchingOwner(logicalAddress);
        this.checkForDuplicateUpdates(engagementTransactionList);

        for (EngagementTransactionTypeImpl engagement : engagementTransactionList) {
            EngagementTypeImpl e = engagement.getEngagement();
            if (engagement.isDeleteFlag()) {
                this.removeEngagement(e);
            } else {
                log.debug("Engagement: {}", e.toString());
                e.setOwner(getOwner());
                this.addEngagement(e);
            }
        }
    }

    /**
     * Sends a message per logical address to a JMS queue.
     *
     * @TODO move this work to a splitter in the camel route.
     * @param incomingOwner
     * @param engagements
     */
    private void sendMessage(String incomingOwner, UpdateType engagements) {
        try {
            log.debug("Sending message to queue.");
            if (connection == null) {
                this.createJMSSession();
            }
            ProcessNotificationType processNotificationType = this.buildProcessNotificationType(engagements);
            for (String logicalAddress : logicalAddressCache.getLogicalAddresses()) {
                if (logicalAddress.equals(incomingOwner)) {
                    log.debug("Skipping notification to owner who called us.");
                    continue;
                }
                if (logicalAddress.equals(configuration.getOwner())) {
                    log.debug("Skipping notification to ourselves.");
                    continue;
                }

                ArrayList<Object> parameter = new ArrayList<Object>();
                parameter.add(logicalAddress);
                parameter.add(processNotificationType);
                ObjectMessage message = session.createObjectMessage(parameter);
                publisher.send(message);
                log.debug("Message sent.");
            }
        } catch (final JMSException ex) {
            log.error("", ex);
        }
    }

    /**
     *
     * @throws JMSException
     */
    private void createJMSSession() throws JMSException {
        connection = cf.createConnection();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        publisher = session.createProducer(eiQueue);
    }

    /**
     * Builds a camel route that takes messages from a jms queue (jndi-name
     * jms:queue:eiQueue) and routes it to a
     * ProcessNotificationResponderInterface (typically a virtual service on
     * TP).
     *
     * @throws Exception
     */
    private void buildCamelRoute() throws Exception {
        CamelContext context = new DefaultCamelContext();
        context.addComponent("jms",
                JmsComponent.jmsComponentAutoAcknowledge(camelJMSConnectionFactory));
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                errorHandler(deadLetterChannel("jms:queue:eiDead").maximumRedeliveries(10).redeliveryDelay(15000));
                from("jms:queue:eiQueue").
                        to("cxf://"
                        + configuration.getVirtualService()
                        + "?serviceClass=riv.itintegration.engagementindex.processnotification._1.rivtabp21.ProcessNotificationResponderInterface&dataFormat=POJO");

            }
        });
        context.start();
    }

    /**
     *
     * @param engagements
     * @return
     */
    private ProcessNotificationType buildProcessNotificationType(UpdateType engagements) {
        ProcessNotificationType pn = new ProcessNotificationType();
        for (EngagementTransactionType e : engagements.getEngagementTransaction()) {
            e.getEngagement().setOwner(configuration.getOwner());
            pn.getEngagementTransaction().add(e);
        }
        return pn;
    }
}